package com.xrui.hbaseview.ui.views;

import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import com.xrui.hbase.ColumnName;
import com.xrui.hbaseview.actions.RunnableAction;
import com.xrui.hbaseview.config.ClusterConfig;
import com.xrui.hbaseview.config.GlobalConfig;
import com.xrui.hbaseview.data.*;
import com.xrui.hbaseview.export.FileExporter;
import com.xrui.hbaseview.filters.EmptyFilter;
import com.xrui.hbaseview.filters.Filter;
import com.xrui.hbaseview.filters.PatternFilter;
import com.xrui.hbaseview.hbase.*;
import com.xrui.hbaseview.system.Clipboard;
import com.xrui.hbaseview.system.ClipboardData;
import com.xrui.hbaseview.system.InMemoryClipboard;
import com.xrui.hbaseview.ui.ChangeTracker;
import com.xrui.hbaseview.ui.MessageHandler;
import com.xrui.hbaseview.ui.UIAction;
import com.xrui.hbaseview.ui.UIUpdateHandler;
import com.xrui.hbaseview.ui.controls.WideComboBox;
import com.xrui.hbaseview.ui.design.*;
import com.xrui.hbaseview.ui.forms.ExportTableDialog;
import com.xrui.hbaseview.ui.forms.ScanDialog;
import com.xrui.hbaseview.ui.forms.TableMetadataDialog;
import com.xrui.hbase.util.ResourceUtils;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.List;
import com.xrui.hbaseview.actions.Action;
/**
 * This class is a main view.
 */
@SuppressWarnings({"MagicNumber", "AnonymousInnerClassWithTooManyMethods"})
public class DesignerView {
    //region Constants
    private final static Logger LOG = LoggerFactory.getLogger(DesignerView.class);
    //endregion

    //region Variables
    private JPanel topPanel;
    private JList tablesList;
    private DefaultListModel tablesListModel;
    private JTable rowsTable;
    private JButton columnPopulate;
    private JTable columnsTable;
    private JButton columnScan;
    private JSpinner rowsNumber;
    private JButton rowsPrev;
    private JButton rowsNext;
    private JLabel rowsTotal;
    private JLabel rowsVisible;
    private JButton columnCheck;
    private JButton rowCopy;
    private JButton tableRefresh;
    private JButton tableCopy;
    private JButton columnUncheck;
    private JSplitPane topSplitPane;
    private JSplitPane innerSplitPane;
    private JLabel columnsNumber;
    private JLabel tablesNumber;
    private JComboBox tableFilters;
    private DefaultComboBoxModel tablesFilterModel;
    private JComboBox columnFilters;
    private DefaultComboBoxModel columnsFilterModel;
    private ItemListener columnsFilterListener;
    private JButton columnJump;
    private JButton rowOpen;
    private JButton tableExport;
    private JButton columnRefresh;
    private JButton tableMetadata;
    private WideComboBox columnNames;
    private JLabel rowsNumberIcon;
    private DefaultTableModel columnsTableModel;
    private DefaultTableModel rowsTableModel;
    private Query lastQuery;
    private QueryScanner scanner;
    private JPanel owner;
    private Connection connection;
    private ChangeTracker changeTracker;
    private Map<TypedColumn, TableColumn> rowsTableRemovedColumns;
    private ClusterConfig clusterConfig;
    private JComboBox cmbColumnTypes;
    private RunnableAction rowsCountAction;
    private JCellEditor readOnlyCellEditor;
    private Map<String, ColumnType> columnTypes;

    //region Public Methods

    //region Constructor
    @SuppressWarnings("unchecked")
    public DesignerView(final JPanel owner, final Connection connection) {

        this.owner = owner;
        this.connection = connection;

        changeTracker = new ChangeTracker();
        rowsTableRemovedColumns = new HashMap<>();
        clusterConfig = new ClusterConfig(connection.getServerName());
        clusterConfig.setConnection(connection.getConnectionDetails());
        tablesFilterModel = new DefaultComboBoxModel();
        tableFilters.setModel(tablesFilterModel);
        columnsFilterModel = new DefaultComboBoxModel();
        columnFilters.setModel(columnsFilterModel);
        columnTypes = new HashMap<>();

        readOnlyCellEditor = new JCellEditor(changeTracker, false);

        fillComboBox(tableFilters, null, clusterConfig.getTableFilters());

        String tableFilter = clusterConfig.getSelectedTableFilter();
        if (tableFilter != null) {
            tableFilters.setSelectedItem(tableFilter);
        }

        initializeTablesList();
        initializeColumnsTable();
        initializeRowsTable();

        // Customize look of the dividers that they will look of the same size and without buttons.
        BasicSplitPaneDivider dividerContainer = (BasicSplitPaneDivider) topSplitPane.getComponent(0);
        dividerContainer.setBorder(BorderFactory.createEmptyBorder(4, 0, 4, 0));
        dividerContainer.setLayout(new BorderLayout());

        dividerContainer = (BasicSplitPaneDivider) innerSplitPane.getComponent(0);
        dividerContainer.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
        dividerContainer.setLayout(new BorderLayout());

        rowsNumber.setModel(new SpinnerNumberModel(100, 1, 10000, 100));

        tableFilters.addItemListener(
            e -> {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    LOG.debug("Filter tables event triggered");
                    clusterConfig.setSelectedTableFilter((String) e.getItem());

                    loadTables();
                }
            });

        tableFilters.getEditor().addActionListener(
            e -> {
                String item = (String) tableFilters.getEditor().getItem();
                if (item != null && !item.isEmpty()) {
                    if (tablesFilterModel.getIndexOf(item) == -1) {
                        tableFilters.addItem(item);
                    }

                    tableFilters.setSelectedItem(item);
                    clusterConfig.setTablesFilter(getFilters(tableFilters));
                } else {
                    Object selectedItem = tableFilters.getSelectedItem();
                    if (selectedItem != null) {
                        tableFilters.removeItem(selectedItem);
                        clusterConfig.setTablesFilter(getFilters(tableFilters));
                    }
                }
            });

        columnsFilterListener = e -> {
            if (e.getStateChange() == ItemEvent.SELECTED) {
                LOG.debug("Filter columns event triggered");
                clusterConfig.setSelectedColumnFilter(getSelectedTableName(), (String) e.getItem());

                populateColumnsTable(false);
            }
        };

        columnFilters.addItemListener(columnsFilterListener);

        columnFilters.getEditor().addActionListener(
            e -> {
                String item = (String) columnFilters.getEditor().getItem();
                if (item != null && !item.isEmpty()) {
                    if (columnsFilterModel.getIndexOf(item) == -1) {
                        columnFilters.addItem(item);
                    }

                    columnFilters.setSelectedItem(item);
                    clusterConfig.setColumnsFilter(getSelectedTableName(), getFilters(columnFilters));
                } else {
                    Object selectedItem = columnFilters.getSelectedItem();
                    if (selectedItem != null) {
                        columnFilters.removeItem(selectedItem);
                        clusterConfig.setColumnsFilter(getSelectedTableName(), getFilters(columnFilters));
                    }
                }
            });

        columnNames.addItemListener(
            e -> {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    clearRows(columnsTable);
                    clearTable(rowsTable);
                    changeTracker.clear();
                    togglePagingControls();
                    toggleColumnControls(false);
                    toggleRowControls(false);
                }
            });

        connection.addListener(
            new HbaseActionListener() {
                @Override
                public void copyOperation(String source, String sourceTable, String target, String targetTable, Result result) {
                    setInfo(
                        String.format(
                            "Copying row '%s' from '%s.%s' to '%s.%s'", Bytes.toStringBinary(result.getRow()), source, sourceTable, target, targetTable));
                }

                @Override
                public void saveOperation(String tableName, String path, Result result) {
                    setInfo(String.format("Saving row '%s' from table '%s' to file '%s'", Bytes.toStringBinary(result.getRow()), tableName, path));
                }

                @Override
                public void loadOperation(String tableName, String path, Put put) {
                    setInfo(String.format("Loading row '%s' to table '%s' from file '%s'", Bytes.toStringBinary(put.getRow()), tableName, path));
                }

                @Override
                public void tableOperation(String tableName, String operation) {
                    setInfo(String.format("The %s table has been %s", tableName, operation));
                }

                @Override
                public void rowOperation(String tableName, DataRow row, String operation) {
                    setInfo(
                        String.format(
                            "The %s row has been %s %s the %s table", row.getKey(), operation, "added".equals(operation) ? "to" : "from", tableName));
                }

                @Override
                public void columnOperation(String tableName, String column, String operation) {
                    setInfo(
                        String.format(
                            "The %s column has been %s %s the %s table", column, operation, "added".equals(operation) ? "to" : "from", tableName));
                }
            });

        columnJump.addActionListener(
            e -> {
                LOG.debug("Column jump event triggered");
                clearError();

                String rowNumber = JOptionPane.showInputDialog(
                    topPanel, "Row number:", "Jump to specific row", JOptionPane.PLAIN_MESSAGE);

                if (rowNumber != null) {
                    try {
                        long offset = Long.parseLong(rowNumber);
                        LOG.info("Jump to row {}...", offset);
                        lastQuery = null;

                        populateRowsTable(offset, Direction.Current);
                    } catch (NumberFormatException ignore) {
                        JOptionPane.showMessageDialog(topPanel, "Row number must be a number.", "Error", JOptionPane.ERROR_MESSAGE);
                    }
                }
            });

        columnPopulate.addActionListener(
            e -> {
                LOG.debug("Scan event triggered");
                clearError();

                lastQuery = null;

                populateRowsTable(Direction.Current);
            });

        columnScan.addActionListener(
            e -> {
                LOG.debug("Advanced scan event triggered");
                clearError();

                ScanDialog dialog;
                try {
                    String tableName = getSelectedTableName();
                    dialog = new ScanDialog(lastQuery, connection.getTableLayout(tableName), connection.getTableDescriptor(tableName));

                    boolean okPressed = dialog.showDialog(topPanel);
                    if (okPressed) {
                        lastQuery = dialog.getQuery();
                        LOG.info("Scanning table {}...", tableName);
                        if (lastQuery != null) {
                            clearRows(columnsTable);
                            clearRows(rowsTable);
                            populateRowsTable(Direction.Current);
                        }
                    }
                } catch (Exception ex) {
                    setError("Failed to scan table", ex);
                }
            });

        rowOpen.addActionListener(
            e -> {
                LOG.debug("Show rows with external viewer  event triggered");
                clearError();

                owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                try {
                    File tempFile = File.createTempFile("h-viewer", GlobalConfig.instance().getExternalViewerFileExtension());
                    FileOutputStream stream = null;

                    try {
                        LOG.debug("Writing rows to file {}...", tempFile.getAbsolutePath());
                        stream = new FileOutputStream(tempFile);
                        FileExporter exporter = new FileExporter(stream, GlobalConfig.instance().getExternalViewerDelimiter());

                        List<TypedColumn> columns = getShownColumns();

                        for (int i = 0; i < rowsTable.getRowCount(); i++) {
                            exporter.write(((DataCell) rowsTable.getValueAt(i, 0)).getRow(), columns);
                        }
                        exporter.close();
                    } finally {
                        if (stream != null) {
                            try {
                                stream.close();
                            } catch (IOException ignore) {
                            }
                        }
                    }

                    Desktop.getDesktop().open(tempFile);
                } catch (Exception ex) {
                    setError("Failed to open rows in external viewer: ", ex);
                } finally {
                    owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                }
            });


        rowCopy.addActionListener(
            e -> {
                LOG.debug("Row copy event triggered");
                clearError();

                JTableModel.stopCellEditing(rowsTable);

                copySelectedRowsToClipboard();
            });

        rowsPrev.addActionListener(
            e -> {
                LOG.debug("Previous row event triggered");
                clearError();

                populateRowsTable(Direction.Backward);
            });

        rowsNext.addActionListener(
            e -> {
                LOG.debug("Next row event triggered");
                clearError();

                populateRowsTable(Direction.Forward);
            });

        columnCheck.addActionListener(
            e -> {
                LOG.debug("Check all columns event triggered");
                clearError();

                owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                try {
                    for (int i = 1; i < columnsTable.getRowCount(); i++) {
                        columnsTable.setValueAt(true, i, 0);
                    }
                } finally {
                    owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                }
            });

        columnUncheck.addActionListener(
            e -> {
                LOG.debug("Uncheck all columns event triggered");
                clearError();

                owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                try {
                    for (int i = 3; i < columnsTable.getRowCount(); i++) {
                        columnsTable.setValueAt(false, i, 0);
                    }
                } finally {
                    owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                }
            });

        tableRefresh.addActionListener(
            e -> {
                LOG.debug("Table refresh event triggered");
                clearError();

                populate();
            });

        tableCopy.addActionListener(
            e -> {
                LOG.debug("Table information copy event triggered");
                clearError();

                copyTableToClipboard();
            });

        tableExport.addActionListener(
            e -> {
                LOG.debug("Table export event triggered");
                clearError();

                String tableName = getSelectedTableName();
                if (tableName != null) {
                    owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                    try {
                        QueryScanner scanner = connection.getScanner(tableName, getColumnName());
                        scanner.setColumnTypes(columnTypes);

                        ExportTableDialog dialog = new ExportTableDialog(scanner);
                        dialog.showDialog(topPanel);
                    } catch (Exception ex) {
                        setError(String.format("Failed to export table %s: ", tableName), ex);
                    } finally {
                        owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                    }
                }
            });

        tableMetadata.addActionListener(
            e -> {
                LOG.debug("Table metadata view event triggered");
                clearError();

                String tableName = getSelectedTableName();
                if (tableName != null) {
                    try {
                        TableDescriptor tableDescriptor = connection.getTableDescriptor(tableName);

                        TableMetadataDialog dialog = new TableMetadataDialog(tableDescriptor);
                        if (dialog.showDialog(topPanel)) {
                            owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                            try {
                                connection.createOrModifyTable(tableDescriptor);
                            } catch (Exception ex) {
                                setError(String.format("Failed to update table %s metadata: ", tableName), ex);
                            } finally {
                                owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                            }
                        }
                    } catch (Exception ex) {
                        setError(String.format("Failed to retrieve descriptor of the table %s: ", tableName), ex);
                    }
                }
            });

        columnRefresh.addActionListener(
            e -> {
                LOG.debug("Column refresh event triggered");
                clearError();
                abortRowsCountAction();

                toggleTableControls();

                scanner = null;
                lastQuery = null;

                String currentFilter = clusterConfig.getSelectedColumnFilter(getSelectedTableName());

                fillComboBox(columnFilters, columnsFilterListener, clusterConfig.getColumnFilters(getSelectedTableName()));
                setFilter(columnFilters, columnsFilterListener, currentFilter);

                //populateColumnsTable(true);
                populateColumnNames(true);
                clearError();
            });
    }

    //endregion

    //region Private Methods

    /**
     * Clears all rows from the specified table model.
     *
     * @param table The table to clear the rows from.
     */
    private static void clearRows(JTable table) {
        ((DefaultTableModel) table.getModel()).setRowCount(0);
    }

    /**
     * Clears all columns from the specified table model.
     *
     * @param table The table to clear the columns from.
     */
    private static void clearColumns(JTable table) {
        ((DefaultTableModel) table.getModel()).setColumnCount(0);

        TableColumnModel cm = table.getColumnModel();
        while (cm.getColumnCount() > 0) {
            cm.removeColumn(cm.getColumn(0));
        }
    }

    /**
     * Clear all data from the table.
     *
     * @param table The table to clear the data from.
     */
    private static void clearTable(JTable table) {
        clearRows(table);
        clearColumns(table);
    }

    /**
     * Gets a column from the specified table.
     *
     * @param columnName The name of the column to look.
     * @param table      The table that should contain column.
     * @return A reference to {@link TableColumn} if found or {@code null} otherwise.
     */
    private static TableColumn getColumn(String columnName, JTable table) {
        for (int i = 0; i < table.getColumnCount(); i++) {
            if (columnName.equals(table.getColumnName(i))) {
                return table.getColumnModel().getColumn(i);
            }
        }
        return null;
    }

    /**
     * Clears the previously set error messages.
     */
    private static void clearError() {
        MessageHandler.addInfo("");
    }

    /**
     * Sets the error message.
     *
     * @param message The error message to set.
     * @param ex      An exception.
     */
    private static void setError(String message, Exception ex) {
        MessageHandler.addError(message, ex);
    }

    /**
     * Sets the info message.
     *
     * @param message The info message to set.
     */
    private static void setInfo(String message) {
        MessageHandler.addInfo(message);
    }

    /**
     * Sets the action.
     *
     * @param action The action to set.
     */
    private static void setAction(UIAction action) {
        MessageHandler.addAction(action);
    }

    /**
     * Sets the specified table to be editable or not.
     *
     * @param table    The table to set.
     * @param editable Indicates whether the table should be editable or not.
     */
    private static void setTableEditable(JTable table, boolean editable) {
        for (int col = 1; col < table.getColumnCount(); col++) {
            TableColumn column = table.getColumnModel().getColumn(col);

            JCellEditor cellEditor = (JCellEditor) column.getCellEditor();
            cellEditor.setEditable(editable);
        }
    }

    /**
     * Sets a filter.
     *
     * @param comboBox A combo box to be set.
     * @param value    A value to be selected on the combo box.
     */
    private static void setFilter(JComboBox comboBox, ItemListener listener, String value) {
        if (listener != null) {
            comboBox.removeItemListener(listener);
        }

        comboBox.setSelectedItem(value);
        comboBox.setToolTipText(value);

        if (listener != null) {
            comboBox.addItemListener(listener);
        }
    }

    /**
     * Fills a combo box with the provided values.
     *
     * @param comboBox A combo box to fill.
     * @param values   A list of values to add.
     */
    @SuppressWarnings("unchecked")
    private static void fillComboBox(JComboBox comboBox, ItemListener listener, Iterable<String> values) {
        if (values != null) {
            if (listener != null) {
                comboBox.removeItemListener(listener);
            }

            comboBox.removeAllItems();

            comboBox.addItem("");

            for (String value : values) {
                comboBox.addItem(value);
            }

            if (listener != null) {
                comboBox.addItemListener(listener);
            }
        }
    }

    /**
     * Gets a list of filters from the provided combo box.
     *
     * @param comboBox A combo box containing the filters to get.
     * @return A list of filters.
     */
    private static Iterable<String> getFilters(JComboBox comboBox) {
        Collection<String> filters = new ArrayList<>();
        for (int i = 0; i < comboBox.getItemCount(); i++) {
            String item = (String) comboBox.getItemAt(i);
            if (item != null && !item.isEmpty()) {
                filters.add(item);
            }
        }
        return filters;
    }

    /**
     * Populates the view with the data.
     */
    public void populate() {
        loadTables();
    }

    /**
     * Gets the currently selected table name.
     *
     * @return The name of the selected table from the list of the tables.
     */
    public String getSelectedTableName() {
        return (String) tablesList.getSelectedValue();
    }

    /**
     * Selects a table from the list of the available tables.
     *
     * @param tableName The name of the table to select.
     */
    public void setSelectedTableName(String tableName) {
        tablesList.setSelectedValue(tableName, true);
    }

    /**
     * Gets the reference to the view.
     *
     * @return A {@link JPanel} that contains the controls.
     */
    public JPanel getView() {
        return topPanel;
    }

    public void saveView() {
        LOG.info("Saving status of view {}", connection.getConnectionDetails().getZookeeper());
        clusterConfig.save();
        ResourceUtils.closeOrLog(scanner);
        ResourceUtils.closeOrLog(connection);
    }

    /**
     * Gets a reference to the class used to access the hbase.
     *
     * @return A reference to the {@link Connection} class.
     */
    public Connection getConnection() {
        return connection;
    }

    /**
     * Loads the table names.
     */
    @SuppressWarnings("unchecked")
    private void loadTables() {
        LOG.info("Loading tables list...");
        owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        try {
            Object selectedTable = tablesList.getSelectedValue();
            clearError();
            tablesListModel.clear();

            Filter filter;

            String value = (String) tableFilters.getSelectedItem();
            if (value == null || value.isEmpty()) {
                filter = new EmptyFilter();
            } else {
                filter = new PatternFilter(value);
            }

            int tablesCount = 0;

            Collection<String> tables = connection.getTables("ecprepcicdf");
            if (tables.isEmpty()) {
                tables.add("ecprepcicdf:MiFIDInstrument_STORE");
                tables.add("ecprepcicdf:MiFIDInstrument_VIEW");
            }
            for (String table : tables) {
                if (filter.match(table) && !TableUtil.isMetaTable(table)) {
                    tablesListModel.addElement(table);
                }
            }

            tablesCount += tables.size();

            toggleTableControls();

            tablesNumber.setText(String.format("%s of %s", tablesListModel.getSize(), tablesCount));
            tablesList.setSelectedValue(selectedTable, true);
            LOG.info("{} tables loaded", tablesCount);
        } catch (Exception ex) {
            setError("Failed to connect to hadoop: ", ex);
        } finally {
            owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Abort the rows count action
     */
    private void abortRowsCountAction() {
        if (rowsCountAction != null) {
            LOG.info("Abort count rows action...");
            rowsCountAction.abort();
            rowsTotal.setText("?");
            rowsVisible.setText("?");
            rowsNumberIcon.setVisible(false);
            rowsTotal.setVisible(true);
        } else {
            LOG.debug("No running rows count action found");
        }
    }

    /**
     * Initializes a table list used to present all available tables in the hbase cluster.
     */
    @SuppressWarnings("unchecked")
    private void initializeTablesList() {
        tablesListModel = new DefaultListModel();
        tablesList.setModel(tablesListModel);
        tablesList.setCellRenderer(new JListRenderer(connection));

        tablesList.addListSelectionListener(
            e -> {
                owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                try {

                    if (!e.getValueIsAdjusting()) {
                        LOG.debug("Table selected by user");
                        abortRowsCountAction();

                        toggleTableControls();

                        boolean populate = false;
                        int[] selectedIndices = tablesList.getSelectedIndices();

                        if (selectedIndices.length == 1) {
                            //populate = tableEnabled(getSelectedTableName());
                            populate = true;
                        }

                        if (populate) {
                            LOG.info("Populating table {}", getSelectedTableName());
                            scanner = null;
                            lastQuery = null;

                            String currentFilter = clusterConfig.getSelectedColumnFilter(getSelectedTableName());

                            fillComboBox(columnFilters, columnsFilterListener, clusterConfig.getColumnFilters(getSelectedTableName()));
                            setFilter(columnFilters, columnsFilterListener, currentFilter);

                            //populateColumnsTable(true);
                            populateColumnNames(true);
                        } else {
                            clearRows(columnsTable);
                            clearTable(rowsTable);
                            changeTracker.clear();
                            togglePagingControls();
                            toggleColumnControls(false);
                            toggleRowControls(false);

                            rowsTotal.setText("?");
                            rowsVisible.setText("?");

                            /*if (selectedIndices.length == 1) {
                                setError("The selected table is disabled", new RuntimeException("Table disabled"));
                            }*/
                        }
                    }
                } finally {
                    owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                }
            });

        tablesList.addKeyListener(
            new KeyAdapter() {
                @Override
                public void keyReleased(KeyEvent e) {
                    if (e.isControlDown()) {
                        if (e.getKeyCode() == KeyEvent.VK_C) {
                            if (tableCopy.isEnabled()) {
                                clearError();
                                copyTableToClipboard();
                            }
                        }
                    }
                }
            });
    }

    /**
     * Initializes a columns table used to present the columns of the selected table.
     */
    @SuppressWarnings("unchecked")
    private void initializeColumnsTable() {
        columnsTableModel = new JTableModel(0, 2);
        columnsTableModel.addColumn("Show");
        columnsTableModel.addColumn("Field Name");
        columnsTableModel.addColumn("Field Type");
        columnsTable.setRowHeight(columnsTable.getFont().getSize() + 8);
        columnsTable.setModel(columnsTableModel);
        columnsTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);

        columnsTable.getColumn("Show").setCellRenderer(new JCheckBoxRenderer(new CheckedRow(1, TypedColumn.KEY)));
        columnsTable.getColumn("Show").setCellEditor(new JCheckBoxRenderer(new CheckedRow(1, TypedColumn.KEY)));
        columnsTable.getColumn("Show").setPreferredWidth(50);
        columnsTable.getColumn("Show").setMaxWidth(50);
        columnsTable.getColumn("Show").setMinWidth(50);

        cmbColumnTypes = new WideComboBox();

        for (ColumnType columnType : ColumnType.getTypes()) {
            cmbColumnTypes.addItem(columnType);
        }

        columnsTable.getColumn("Field Type").setCellEditor(new DefaultCellEditor(cmbColumnTypes));
        columnsTable.getColumn("Field Type").setPreferredWidth(82);
        columnsTable.getColumn("Field Type").setMaxWidth(300);
        columnsTable.getColumn("Field Type").setMinWidth(82);

        columnsTable.getModel().addTableModelListener(
            e -> {
                if (UIUpdateHandler.enter("tableChanged")) {
                    try {
                        int column = e.getColumn();
                        TableModel model = (TableModel) e.getSource();

                        if (column == 0) {
                            TypedColumn typedColumn = (TypedColumn) model.getValueAt(e.getFirstRow(), 1);
                            boolean isShown = (Boolean) model.getValueAt(e.getFirstRow(), 0);
                            LOG.debug("Visible of column {} changed to {}", typedColumn.getName(), isShown);
                            clusterConfig.setTableConfig(getSelectedTableName(), typedColumn.getName(), "isShown", Boolean.toString(isShown));

                            setRowsTableColumnVisible(typedColumn, isShown);
                        } else if (column == 2) {
                            TypedColumn typedColumn = (TypedColumn) model.getValueAt(e.getFirstRow(), 1);
                            ColumnType type = (ColumnType) model.getValueAt(e.getFirstRow(), column);

                            LOG.debug("Update column {} type to {}", typedColumn.getName(), type);
                            if (!updateColumnType(typedColumn, type)) {
                                columnsTable.setValueAt(getColumnType(typedColumn.getName()), e.getFirstRow(), 2);
                            }
                        }
                    } finally {
                        UIUpdateHandler.leave("tableChanged");
                    }
                }
            });

        columnsTable.addMouseMotionListener(
            new MouseMotionAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    Point p = e.getPoint();
                    int row = columnsTable.rowAtPoint(p);
                    int column = columnsTable.columnAtPoint(p);
                    if (row != -1 && column != -1) {
                        columnsTable.setToolTipText(String.valueOf(columnsTable.getValueAt(row, column)));
                    }
                }
            });
    }

    /**
     * Initializes a rows table used to present the content of the selected table.
     */
    private void initializeRowsTable() {
        rowsTableModel = new DefaultTableModel();
        rowsTable.setModel(rowsTableModel);
        rowsTable.setTableHeader(new ResizeableTableHeader(rowsTable.getColumnModel()));
        rowsTable.setCellSelectionEnabled(false);
        rowsTable.setRowSelectionAllowed(true);
        rowsTable.setAutoCreateColumnsFromModel(false);

        rowsTable.getSelectionModel().addListSelectionListener(
            e -> toggleRowControls(true));

        rowsTable.addMouseMotionListener(
            new MouseMotionAdapter() {
                @Override
                public void mouseMoved(MouseEvent e) {
                    Point p = e.getPoint();
                    int row = rowsTable.rowAtPoint(p);
                    int column = rowsTable.columnAtPoint(p);
                    if (row != -1 && column != -1) {
                        rowsTable.setToolTipText(String.valueOf(rowsTable.getValueAt(row, column)));
                    }
                }
            });

        rowsTable.addKeyListener(
            new KeyAdapter() {
                @Override
                public void keyReleased(KeyEvent e) {
                    if (e.isControlDown()) {
                        if (e.getKeyCode() == KeyEvent.VK_C) {
                            if (rowCopy.isEnabled()) {
                                clearError();
                                JTableModel.stopCellEditing(rowsTable);
                                copySelectedRowsToClipboard();
                            }
                        }
                    }
                }
            });

        rowsTable.addPropertyChangeListener(
            evt -> {
                if ("model".equals(evt.getPropertyName())) {
                    changeTracker.clear();

                    toggleRowControls(false);
                }
            });

        rowsTable.getColumnModel().addColumnModelListener(
            new TableColumnModelListener() {
                @Override
                public void columnAdded(TableColumnModelEvent e) {
                }

                @Override
                public void columnRemoved(TableColumnModelEvent e) {
                }

                @Override
                public void columnMoved(TableColumnModelEvent e) {
                }

                @Override
                public void columnMarginChanged(ChangeEvent e) {
                    TableColumn column = rowsTable.getTableHeader().getResizingColumn();
                    if (column != null) {
                        clusterConfig.setTableConfig(getSelectedTableName(), column.getHeaderValue().toString(), "size", String.valueOf(column.getWidth()));
                    }
                }

                @Override
                public void columnSelectionChanged(ListSelectionEvent e) {
                }
            });
    }

    /**
     * Populates the column names combox from the selected table.
     *
     * @param clearRows Indicates whether the rows table should be cleared.
     */
    @SuppressWarnings("unchecked")
    private void populateColumnNames(boolean clearRows) {
        owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        try {
            LOG.info("Loading column names...");
            if (clearRows) {
                clearRows(columnsTable);
                clearTable(rowsTable);
                changeTracker.clear();
                rowsTotal.setText("?");
                rowsVisible.setText("?");
                rowsNumber.setEnabled(true);
                columnsNumber.setText("0 of 0");
            }
            columnNames.removeAllItems();
            Collection<ColumnName> allColumnNames = clusterConfig.getColumnNames(getSelectedTableName());
            if (allColumnNames.isEmpty()) {
                LOG.debug("Cache miss for table column names");
                allColumnNames = connection.getTableLayout(getSelectedTableName()).getColumns();
            } else {
                LOG.debug("Cache hit for table column names");
            }

            for (ColumnName columnName : allColumnNames) {
                columnNames.addItem(columnName);
            }
            LOG.debug("Loaded column names: {}", allColumnNames);

            if (allColumnNames.size() > 0) {
                columnNames.setSelectedItem(allColumnNames.iterator().next());
                if (scanner == null) {
                    scanner = connection.getScanner(getSelectedTableName(), getColumnName());
                }
                scanner.setColumnName(getColumnName());
                clusterConfig.setColumnNames(getSelectedTableName(), allColumnNames);
            }

            togglePagingControls();
            toggleColumnControls(true);
            toggleRowControls(rowsTableModel.getRowCount() > 0);
        } catch (Exception e) {
            setError("Failed to load column names", e);
        } finally {
            owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Populates a columns table with the list of the columns from the selected table.
     *
     * @param clearRows Indicates whether the rows table should be cleared.
     */
    private void populateColumnsTable(boolean clearRows) {
        owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        try {
            clearRows(columnsTable);

            if (clearRows) {
                clearTable(rowsTable);
                changeTracker.clear();
                rowsTotal.setText("?");
                rowsVisible.setText("?");
                rowsNumber.setEnabled(true);
            }

            String tableName = getSelectedTableName();
            if (tableName != null) {
                loadColumns(tableName);
            }

            togglePagingControls();
            toggleColumnControls(columnsTableModel.getRowCount() > 0);
            toggleRowControls(rowsTableModel.getRowCount() > 0);
        } finally {
            owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Populates a rows table. The method loads the table content. The number of loaded rows depends on the parameter defined by the user
     * in the {@link DesignerView#rowsNumber} control.
     *
     * @param direction Defines what rows should be presented to the user. {@link Direction#Current},
     *                  {@link Direction#Forward} or {@link Direction#Backward}.
     */
    private void populateRowsTable(Direction direction) {
        changeTracker.clear();
        populateRowsTable(0, direction);
    }

    /**
     * Populates a rows table. The method loads the table content. The number of loaded rows depends on the parameter defined by the user
     * in the {@link DesignerView#rowsNumber} control.
     *
     * @param offset    The first row to start loading from.
     * @param direction Defines what rows should be presented to the user. {@link Direction#Current},
     *                  {@link Direction#Forward} or {@link Direction#Backward}.
     */
    private void populateRowsTable(long offset, Direction direction) {
        LOG.info("Populating rows table...");
        owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        try {
            JTableModel.stopCellEditing(columnsTable);

            String tableName = getSelectedTableName();
            if (tableName != null) {
                clearTable(rowsTable);
                changeTracker.clear();
                scanner.setColumnTypes(columnTypes);
                scanner.setQuery(lastQuery);

                Collection<DataRow> rows;

                if (direction == Direction.Current) {
                    rows = scanner.current(offset, getPageSize());
                } else if (direction == Direction.Forward) {
                    LOG.info("Got to next page...");
                    rows = scanner.next(getPageSize());
                } else {
                    LOG.info("Got to previous page...");
                    rows = scanner.prev();
                }

                String summary;
                if (lastQuery != null) {
                    summary = String.format("for query '%s'", lastQuery.summarize());
                } else {
                    summary = String.format("for table '%s'", tableName);
                }
                if (rows.size() > 0) {
                    loadColumns(tableName);
                    loadRowsTableColumns(tableName);
                    setInfo(String.format("%s rows loaded %s", rows.size(), summary));
                } else {
                    setInfo(String.format("No data found %s", summary));
                }
                loadRows(rows);

                togglePagingControls();
                String visible = String.format("%s - %s", scanner.getLastRow() - rows.size() + 1, scanner.getLastRow());
                rowsVisible.setText(visible);
                rowsNumber.setEnabled(scanner.getLastRow() <= getPageSize());
                LOG.info("Totally {} rows populated with rows '{}'", rows.size(), visible);

                rowsCountAction = RunnableAction.run(
                    tableName + "-rowsCount", new Action<Boolean>() {

                        @Override
                        public Boolean run() throws IOException {
                            LOG.info("Calculating records count of table {}...", getSelectedTableName());
                            rowsNumberIcon.setVisible(true);
                            rowsTotal.setVisible(false);

                            long totalNumberOfRows = scanner.getRowsCount(GlobalConfig.instance().getRowCountTimeout());
                            if (scanner.isRowsCountPartiallyCalculated()) {
                                rowsTotal.setText("more than " + totalNumberOfRows);
                                LOG.info("More than {} rows found", totalNumberOfRows);
                            } else {
                                rowsTotal.setText(String.valueOf(totalNumberOfRows));
                                LOG.info("Totally {} rows found", totalNumberOfRows);
                            }

                            rowsNumberIcon.setVisible(false);
                            rowsTotal.setVisible(true);

                            togglePagingControls();

                            return true;
                        }

                        @Override
                        public void onError(Exception ex) {
                            setError("Failed to get the number of rows in the table.", ex);
                        }
                    });
            }

            toggleRowControls(rowsTableModel.getRowCount() > 0);
            toggleColumnControls(columnsTable.getRowCount() > 0);
            toggleColumnPopulateControls(true);
        } catch (Exception ex) {
            setError("Failed to fill rows: ", ex);
        } finally {
            owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Populates rows of the specified column. This method is used when the column which wasn't initially populated is checked.
     *
     * @param column The name of the column which rows are need to be populated.
     * @param rows   The data to populate.
     */
    private void populateColumnOnRowsTable(TypedColumn column, Iterable<DataRow> rows) {

        owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        try {
            int rowIndex = 0;
            int columnIndex = rowsTable.getColumnModel().getColumnIndex(column);

            for (DataRow row : rows) {
                DataCell cell = row.getCell(column);
                if (cell == null) {
                    cell = new DataCell(row, column, new ConvertibleObject(getColumnType(column.getName()), null));
                }

                if (rowIndex >= rowsTable.getRowCount()) {
                    break;
                }

                rowsTableModel.setValueAt(cell, rowIndex++, columnIndex);
            }
        } finally {
            owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Loads columns into the columns table.
     *
     * @param tableName The name of the table which columns to be populated.
     */
    private void loadColumns(String tableName) {
        LOG.info("Loading columns into the columns table...");
        clearRows(columnsTable);

        columnTypes.clear();

        try {
            Filter filter;

            String value = (String) columnFilters.getSelectedItem();
            if (value == null || value.isEmpty()) {
                filter = new EmptyFilter();
            } else {
                filter = new PatternFilter(value);
            }
            DataRow row = scanner.current().iterator().next();
            if (row == null) {
                scanner.setColumnTypes(
                    new HashMap<String, ColumnType>() {{
                        put(TypedColumn.KEY.getName(), ColumnType.String);
                    }});

                row = scanner.getFirstRow();
            }

            Collection<TypedColumn> columns = scanner.getColumns();
            for (TypedColumn column : columns) {
                boolean isColumnVisible = column.isKey() || filter.match(column.getName());
                if (isColumnVisible) {
                    addColumnToColumnsTable(tableName, column, row);
                }

                setRowsTableColumnVisible(column, isColumnVisible && isShown(tableName, column.getName()));
            }
            LOG.info("{} columns loaded", columns.size());
            columnsNumber.setText(String.format("%s of %s", columnsTableModel.getRowCount(), columns.size()));
        } catch (Exception ex) {
            setError("Failed to fill tables list: ", ex);
        }
    }

    /**
     * Adds a column to the columns table.
     *
     * @param tableName   The name of the table the column belongs to. Used to look for the column type in the saved configuration.
     * @param typedColumn The column qualifier to add.
     * @param row         The row that might contain column type information.
     */
    private void addColumnToColumnsTable(String tableName, TypedColumn typedColumn, DataRow row) {
        ColumnType columnType = getSavedColumnType(tableName, typedColumn.getName());

        if (columnType == null && row != null) {
            DataCell cell = row.getCell(typedColumn);
            if (cell != null) {
                if (typedColumn.getType() != null) {
                    columnType = typedColumn.getType();
                } else {
                    columnType = cell.guessType();
                }
            }
        }

        if (typedColumn.getName().equals(TypedColumn.TIMESTAMP.getName())) {
            columnType = ColumnType.DateAsLong;
        }

        if (columnType == null) {
            columnType = ColumnType.fromColumn(typedColumn.getName());
        }

        if (!columnType.equals(typedColumn.getType())) {
            updateColumnType(typedColumn, columnType);
        }

        boolean isShown = isShown(tableName, typedColumn.getName());

        columnsTableModel.addRow(new Object[]{isShown, typedColumn, columnType});
        columnTypes.put(typedColumn.getName(), columnType);
    }

    /**
     * Adds columns defined in the columns table to the rows table.
     */
    private void loadRowsTableColumns(String tableName) {
        clearColumns(rowsTable);

        rowsTableRemovedColumns.clear();

        for (int i = 0, j = 0; i < columnsTable.getRowCount(); i++) {
            TypedColumn typedColumn = (TypedColumn) columnsTableModel.getValueAt(i, 1);

            boolean isShown = (Boolean) columnsTableModel.getValueAt(i, 0);
            if (isShown) {
                addColumnToRowsTable(tableName, typedColumn, j++);
            }
        }

        setTableEditable(rowsTable, false);
    }

    /**
     * Shows or hides a column in rows table.
     *
     * @param typedColumn The name of the column.
     * @param isVisible   Indicates if the columns should be shown or hidden.
     */
    private void setRowsTableColumnVisible(TypedColumn typedColumn, boolean isVisible) {
        if (rowsTable.getRowCount() > 0) {
            if (isVisible) {
                TableColumn tableColumn = rowsTableRemovedColumns.get(typedColumn);
                if (tableColumn != null) {
                    rowsTable.addColumn(tableColumn);
                    rowsTableRemovedColumns.remove(typedColumn);

                    rowsTable.moveColumn(rowsTable.getColumnCount() - 1, getColumnIndex(typedColumn.getName()));
                } else {
                    if (getColumn(typedColumn.getName(), rowsTable) == null) {
                        addColumnToRowsTable(getSelectedTableName(), typedColumn, rowsTable.getColumnCount());

                        if (scanner != null) {
                            populateColumnOnRowsTable(typedColumn, scanner.current());
                        }

                        rowsTable.moveColumn(rowsTable.getColumnCount() - 1, getColumnIndex(typedColumn.getName()));
                    }
                }
            } else {
                TableColumn tableColumn = getColumn(typedColumn.getName(), rowsTable);
                if (tableColumn != null) {
                    rowsTable.removeColumn(tableColumn);
                    rowsTableRemovedColumns.put(typedColumn, tableColumn);
                }
            }
        }
    }

    /**
     * Adds a single column to the rows table.
     *
     * @param tableName   The name of the table the column belongs to.
     * @param typedColumn The name of the column to add.
     * @param columnIndex The index the column should be added at.
     */
    private void addColumnToRowsTable(String tableName, TypedColumn typedColumn, int columnIndex) {
        TableColumn tableColumn = new TableColumn(columnIndex);
        tableColumn.setIdentifier(typedColumn);
        tableColumn.setHeaderValue(typedColumn);
        //tableColumn.setCellEditor(ColumnQualifier.isKey(qualifier.getFullName()) ? readOnlyCellEditor : editableCellEditor);
        tableColumn.setCellEditor(readOnlyCellEditor);
        tableColumn.setHeaderRenderer(
            (table, value, isSelected, hasFocus, row, column) -> {
                TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer();

                JComponent component = (JComponent) renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
                component.setToolTipText(table.getColumnName(column));

                return component;
            });

        try {
            Integer width = clusterConfig.getTableConfig(Integer.class, tableName, typedColumn.getName(), "size");
            if (width != null) {
                tableColumn.setPreferredWidth(width);
            }
        } catch (NumberFormatException ignore) {
        }

        rowsTable.addColumn(tableColumn);
        rowsTableModel.addColumn(typedColumn);
    }

    /**
     * Adds rows to the rows table.
     *
     * @param rows A list of rows to add.
     */
    private void loadRows(Iterable<DataRow> rows) {
        clearRows(rowsTable);

        for (DataRow row : rows) {
            Collection<Object> values = new ArrayList<>(rowsTable.getColumnCount());

            Enumeration<TableColumn> columns = rowsTable.getColumnModel().getColumns();
            while (columns.hasMoreElements()) {
                TableColumn column = columns.nextElement();
                String columnName = column.getHeaderValue().toString();

                DataCell cell = row.getCell(columnName);
                if (cell == null) {
                    cell = new DataCell(row, new TypedColumn(columnName, ColumnType.BinaryString), new ConvertibleObject(getColumnType(columnName), null));
                }
                values.add(cell);
            }

            rowsTableModel.addRow(values.toArray());
        }
    }

    /**
     * Copies selected rows from the rows table to the clipboard.
     */
    private void copySelectedRowsToClipboard() {
        owner.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        try {
            int[] selectedRows = rowsTable.getSelectedRows();
            if (selectedRows.length > 0) {
                DataTable table = new DataTable(getSelectedTableName());
                for (int selectedRow : selectedRows) {
                    DataCell cell = (DataCell) rowsTable.getValueAt(selectedRow, 0);
                    table.addRow(cell.getRow());
                }

                Clipboard.setText(table.toString());
                InMemoryClipboard.setData(new ClipboardData<>(table));
            } else {
                InMemoryClipboard.setData(null);
            }
        } finally {
            owner.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
    }

    /**
     * Copies selected table to the clipboard.
     */
    private void copyTableToClipboard() {
        String tableName = getSelectedTableName();
        if (tableName != null) {
            Clipboard.setText(tableName);
            InMemoryClipboard.setData(new ClipboardData<>(new DataTable(tableName, connection)));
        } else {
            InMemoryClipboard.setData(null);
        }
    }

    /**
     * Gets a list of columns that are checked. Checked columns are the columns to be shown in the rows table.
     *
     * @return A list of checked columns from the columns table.
     */
    private Collection<TypedColumn> getShownTypedColumns() {
        Collection<TypedColumn> typedColumns = new ArrayList<>();
        for (int i = 0; i < columnsTable.getRowCount(); i++) {
            boolean isShown = (Boolean) columnsTableModel.getValueAt(i, 0);
            if (isShown) {
                typedColumns.add(
                    new TypedColumn(
                        (String) columnsTableModel.getValueAt(i, 1), (ColumnType) columnsTableModel.getValueAt(i, 2)));
            }
        }
        return typedColumns;
    }

    /**
     * Gets a list of column names from the rows table.
     *
     * @return A list of column names.
     */
    private List<TypedColumn> getShownColumns() {
        List<TypedColumn> typedColumns = new ArrayList<>();
        for (int i = 0; i < columnsTable.getRowCount(); i++) {
            boolean isShown = (Boolean) columnsTableModel.getValueAt(i, 0);
            if (isShown) {
                typedColumns.add((TypedColumn) columnsTableModel.getValueAt(i, 1));
            }
        }
        return typedColumns;
    }

    /**
     * Gets the page size. The page size is the number of rows to be loaded in a batch.
     *
     * @return The size of the page.
     */
    private int getPageSize() {
        return (Integer) rowsNumber.getValue();
    }

    /**
     * Gets the index of the column as it appear in the columns table. The index is calculated only for checked columns.
     *
     * @param columnName The name of the column which index should be calculated.
     * @return The index of the specified column.
     */
    private int getColumnIndex(String columnName) {
        for (int i = 0, j = 0; i < columnsTable.getRowCount(); i++) {
            TypedColumn qualifier = (TypedColumn) columnsTableModel.getValueAt(i, 1);

            boolean isShown = (Boolean) columnsTableModel.getValueAt(i, 0);
            if (isShown) {
                if (qualifier.getName().equals(columnName)) {
                    return j;
                }

                j++;
            }
        }
        return 0;
    }

    /**
     * Gets a list of the selected tables.
     *
     * @return A list of the selected tables.
     */
    private List<String> getSelectedTables() {
        List<String> tables = new ArrayList<>();
        for (Object table : tablesList.getSelectedValuesList()) {
            tables.add(table.toString());
        }
        return tables;
    }

    /**
     * Gets the type of the column from the configuration.
     *
     * @param tableName  The name of the table that contains the column.
     * @param columnName The name of the column.
     * @return The column type.
     */
    private ColumnType getSavedColumnType(String tableName, String columnName) {
        String columnType = clusterConfig.getTableConfig(String.class, tableName, columnName);
        if (columnType != null) {
            return ColumnType.fromName(columnType);
        }
        return null;
    }

    /**
     * Gets the type of the column from the columns table.
     *
     * @param columnName The name of the column.
     * @return The columns type
     */
    private ColumnType getColumnType(String columnName) {
        ColumnType columnType = columnTypes.get(columnName);
        if (columnType == null) {
            return getSavedColumnType(getSelectedTableName(), columnName);
        }
        return columnType;
    }

    /**
     * Gets the column name.
     *
     * @return A selected column name.
     */
    private ColumnName getColumnName() {
        return (ColumnName) columnNames.getSelectedItem();
    }

    /**
     * Gets a column type that is currently selected and can be edited. The column type of the column name gets higher priority.
     *
     * @return A selected and editable column type if exists or null otherwise.
     */
    private ColumnType getSelectedEditableColumnType() {
        ColumnType type = (ColumnType) columnNames.getSelectedItem();
        if (type != null && type.isEditable()) {
            return type;
        }

        int row = columnsTable.getSelectedRow();
        if (row != -1) {
            type = (ColumnType) columnsTable.getValueAt(row, 2);
            if (type.isEditable()) {
                return type;
            }
        }

        return null;
    }

    /**
     * Checks in the configuration whether the specified column is checked.
     *
     * @param tableName  The name of the table that contains the column.
     * @param columnName The name of the column.
     * @return True if the specified column is checked or False otherwise.
     */
    private boolean isShown(String tableName, String columnName) {
        Boolean isShown = clusterConfig.getTableConfig(Boolean.class, tableName, columnName, "isShown");
        return isShown == null || isShown;
    }

    /**
     * Reloads column types.
     */
    @SuppressWarnings("unchecked")
    private void reloadColumnTypes() {
        ColumnType selectedNameType = (ColumnType) columnNames.getSelectedItem();

        cmbColumnTypes.removeAllItems();
        columnNames.removeAllItems();

        for (ColumnType columnType : ColumnType.getTypes()) {
            cmbColumnTypes.addItem(columnType);
        }

        for (ColumnType columnType : ColumnType.getNameTypes()) {
            columnNames.addItem(columnType);
        }

        if (selectedNameType != null) {
            columnNames.setSelectedItem(selectedNameType);
        }
    }

    /**
     * Updates the type of the specified column including already loaded values to the rows table.
     *
     * @param typedColumn The column to update.
     * @param type        The new column type.
     */
    private boolean updateColumnType(TypedColumn typedColumn, ColumnType type) {
        clusterConfig.setTableConfig(getSelectedTableName(), typedColumn.getName(), type.toString());

        JTableModel.stopCellEditing(rowsTable);

        if (scanner != null) {
            try {
                if (scanner.isColumnOfType(typedColumn.getName(), type)) {
                    scanner.updateColumnType(typedColumn.getName(), type);
                    columnTypes.put(typedColumn.getName(), type);

                    rowsTable.updateUI();
                } else {
                    setError(String.format("The selected type '%s' does not match the data.", type), null);
                    return false;
                }
            } catch (Exception ex) {
                setError(String.format("The selected type '%s' does not match the data.", type), ex);
                return false;
            }
        }

        return true;
    }

    /**
     * Updates column type for the column name.
     *
     * @param columnType The new column type.
     */
    private void updateColumnType(ColumnType columnType) {
        for (int col = 0; col < rowsTable.getColumnCount(); col++) {
            TableColumn column = rowsTable.getColumnModel().getColumn(col);

            TypedColumn typedColumn = (TypedColumn) column.getIdentifier();
            typedColumn.setType(columnType);
        }

        for (int row = 0; row < columnsTable.getRowCount(); row++) {
            TypedColumn typedColumn = (TypedColumn) columnsTable.getValueAt(row, 1);
            typedColumn.setType(columnType);
        }

        columnsTable.updateUI();

        if (!rowsTableRemovedColumns.isEmpty()) {
            Map<TypedColumn, TableColumn> removedColumns = new HashMap<>();
            for (TableColumn column : rowsTableRemovedColumns.values()) {
                TypedColumn typedColumn = (TypedColumn) column.getIdentifier();
                typedColumn.setType(columnType);

                removedColumns.put(typedColumn, column);
            }
            rowsTableRemovedColumns = removedColumns;
        }
    }

    /**
     * Enables or disables the paging buttons.
     */
    private void togglePagingControls() {
        rowsPrev.setEnabled(scanner != null && scanner.hasPrev());
        rowsNext.setEnabled(scanner != null && scanner.hasNext());
    }

    private void toggleTableControls() {
        if (tablesListModel.getSize() > 0) {
            int[] selectedIndices = tablesList.getSelectedIndices();
            if (selectedIndices.length == 1) {
                tableCopy.setEnabled(true);
                tableExport.setEnabled(true);
                tableMetadata.setEnabled(true);
            }
        } else {
            tableCopy.setEnabled(false);
            tableExport.setEnabled(false);
            tableMetadata.setEnabled(false);
        }
    }

    private void toggleColumnControls(boolean enabled) {
        columnRefresh.setEnabled(enabled);
        columnFilters.setEnabled(enabled);
        columnScan.setEnabled(enabled);
        columnJump.setEnabled(enabled);
        columnPopulate.setEnabled(enabled);
        columnCheck.setEnabled(columnsTable.getRowCount() > 0);
        columnUncheck.setEnabled(columnsTable.getRowCount() > 0);
        columnNames.setEnabled(enabled);
    }

    private void toggleColumnPopulateControls(boolean enabled) {
        columnScan.setEnabled(enabled);
        columnPopulate.setEnabled(enabled);
        columnNames.setEnabled(enabled);
        columnRefresh.setEnabled(enabled);
    }

    private void toggleRowControls(boolean enabled) {
        int count = rowsTable.getSelectedRowCount();

        rowOpen.setEnabled(enabled && rowsTable.getRowCount() > 0);
        rowCopy.setEnabled(enabled && count > 0);
    }

    private boolean tableEnabled(String tableName) {
        boolean enabled = true;

        try {
            enabled = connection.tableEnabled(tableName);
        } catch (Exception ex) {
            setError(String.format("Failed to access table '%s' information.", tableName), ex);
        }

        return enabled;
    }

    {
// GUI initializer generated by IntelliJ IDEA GUI Designer
// >>> IMPORTANT!! <<<
// DO NOT EDIT OR ADD ANY CODE HERE!
        $$$setupUI$$$();
    }

    /**
     * Method generated by IntelliJ IDEA GUI Designer
     * >>> IMPORTANT!! <<<
     * DO NOT edit this method OR call it in your code!
     *
     * @noinspection ALL
     */
    private void $$$setupUI$$$() {
        topPanel = new JPanel();
        topPanel.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
        topSplitPane = new JSplitPane();
        topSplitPane.setContinuousLayout(true);
        topSplitPane.setDividerSize(9);
        topSplitPane.setOneTouchExpandable(true);
        topPanel.add(topSplitPane, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        topSplitPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(), null));
        final JPanel panel1 = new JPanel();
        panel1.setLayout(new GridLayoutManager(4, 1, new Insets(0, 0, 0, 0), -1, -1));
        panel1.setAlignmentX(0.5f);
        panel1.setAlignmentY(0.5f);
        panel1.setMaximumSize(new Dimension(-1, -1));
        panel1.setMinimumSize(new Dimension(-1, -1));
        panel1.setPreferredSize(new Dimension(230, -1));
        topSplitPane.setLeftComponent(panel1);
        panel1.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(), null));
        final JPanel panel2 = new JPanel();
        panel2.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
        panel2.setAlignmentX(0.5f);
        panel2.setAlignmentY(0.5f);
        panel1.add(panel2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_NORTH, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JToolBar toolBar1 = new JToolBar();
        toolBar1.setFloatable(false);
        panel2.add(toolBar1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 20), null, 0, false));
        tableRefresh = new JButton();
        tableRefresh.setEnabled(true);
        tableRefresh.setHorizontalAlignment(0);
        tableRefresh.setIcon(new ImageIcon(getClass().getResource("/images/db-refresh.png")));
        tableRefresh.setMaximumSize(new Dimension(24, 24));
        tableRefresh.setMinimumSize(new Dimension(24, 24));
        tableRefresh.setPreferredSize(new Dimension(24, 24));
        tableRefresh.setText("");
        tableRefresh.setToolTipText("Refresh tables");
        toolBar1.add(tableRefresh);
        final JToolBar.Separator toolBar$Separator1 = new JToolBar.Separator();
        toolBar1.add(toolBar$Separator1);
        tableFilters = new JComboBox();
        tableFilters.setAlignmentX(0.0f);
        tableFilters.setAlignmentY(0.6f);
        tableFilters.setEditable(true);
        tableFilters.setFont(new Font(tableFilters.getFont().getName(), tableFilters.getFont().getStyle(), tableFilters.getFont().getSize()));
        tableFilters.setMaximumSize(new Dimension(32767, 26));
        tableFilters.setMinimumSize(new Dimension(50, 20));
        tableFilters.setPreferredSize(new Dimension(-1, -1));
        toolBar1.add(tableFilters);
        final JScrollPane scrollPane1 = new JScrollPane();
        scrollPane1.setAlignmentX(0.5f);
        scrollPane1.setAlignmentY(0.5f);
        panel1.add(scrollPane1, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, new Dimension(256, 286), null, 0, false));
        tablesList = new JList();
        tablesList.setSelectionMode(2);
        scrollPane1.setViewportView(tablesList);
        final JPanel panel3 = new JPanel();
        panel3.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
        panel1.add(panel3, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JToolBar toolBar2 = new JToolBar();
        toolBar2.setBorderPainted(false);
        toolBar2.setFloatable(false);
        toolBar2.setRollover(true);
        toolBar2.putClientProperty("JToolBar.isRollover", Boolean.TRUE);
        panel3.add(toolBar2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 20), null, 0, false));
        tableCopy = new JButton();
        tableCopy.setEnabled(false);
        tableCopy.setIcon(new ImageIcon(getClass().getResource("/images/db-copy.png")));
        tableCopy.setMaximumSize(new Dimension(24, 24));
        tableCopy.setMinimumSize(new Dimension(24, 24));
        tableCopy.setPreferredSize(new Dimension(24, 24));
        tableCopy.setText("");
        tableCopy.setToolTipText("Copy table information to the clipboard");
        toolBar2.add(tableCopy);
        tableExport = new JButton();
        tableExport.setEnabled(false);
        tableExport.setIcon(new ImageIcon(getClass().getResource("/images/db-export.png")));
        tableExport.setMaximumSize(new Dimension(24, 24));
        tableExport.setMinimumSize(new Dimension(24, 24));
        tableExport.setPreferredSize(new Dimension(24, 24));
        tableExport.setText("");
        tableExport.setToolTipText("Export table to the file");
        toolBar2.add(tableExport);
        tableMetadata = new JButton();
        tableMetadata.setEnabled(false);
        tableMetadata.setIcon(new ImageIcon(getClass().getResource("/images/db-metadata.png")));
        tableMetadata.setMaximumSize(new Dimension(24, 24));
        tableMetadata.setMinimumSize(new Dimension(24, 24));
        tableMetadata.setPreferredSize(new Dimension(24, 24));
        tableMetadata.setText("");
        tableMetadata.setToolTipText("Show table's metadata");
        toolBar2.add(tableMetadata);
        final JPanel panel4 = new JPanel();
        panel4.setLayout(new GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1));
        panel1.add(panel4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JLabel label1 = new JLabel();
        label1.setText("Tables:");
        panel4.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer1 = new Spacer();
        panel4.add(spacer1, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        tablesNumber = new JLabel();
        tablesNumber.setText("0 of 0");
        panel4.add(tablesNumber, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        innerSplitPane = new JSplitPane();
        innerSplitPane.setContinuousLayout(true);
        innerSplitPane.setDividerSize(9);
        innerSplitPane.setOneTouchExpandable(true);
        topSplitPane.setRightComponent(innerSplitPane);
        innerSplitPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0), null));
        final JPanel panel5 = new JPanel();
        panel5.setLayout(new GridLayoutManager(4, 1, new Insets(0, 0, 0, 2), -1, -1));
        panel5.setAlignmentX(0.5f);
        panel5.setAlignmentY(0.5f);
        panel5.setMaximumSize(new Dimension(-1, -1));
        panel5.setMinimumSize(new Dimension(-1, -1));
        panel5.setPreferredSize(new Dimension(250, -1));
        innerSplitPane.setLeftComponent(panel5);
        final JPanel panel6 = new JPanel();
        panel6.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
        panel5.add(panel6, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JToolBar toolBar3 = new JToolBar();
        toolBar3.setFloatable(false);
        panel6.add(toolBar3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 20), null, 0, false));
        columnRefresh = new JButton();
        columnRefresh.setEnabled(false);
        columnRefresh.setHorizontalAlignment(0);
        columnRefresh.setIcon(new ImageIcon(getClass().getResource("/images/refresh.png")));
        columnRefresh.setMaximumSize(new Dimension(24, 24));
        columnRefresh.setMinimumSize(new Dimension(24, 24));
        columnRefresh.setPreferredSize(new Dimension(24, 24));
        columnRefresh.setText("");
        columnRefresh.setToolTipText("Refresh columns");
        toolBar3.add(columnRefresh);
        final JToolBar.Separator toolBar$Separator2 = new JToolBar.Separator();
        toolBar3.add(toolBar$Separator2);
        columnFilters = new JComboBox();
        columnFilters.setAlignmentX(0.0f);
        columnFilters.setAlignmentY(0.6f);
        columnFilters.setEditable(true);
        columnFilters.setMaximumSize(new Dimension(32767, 26));
        columnFilters.setMinimumSize(new Dimension(50, 20));
        columnFilters.setPreferredSize(new Dimension(-1, -1));
        toolBar3.add(columnFilters);
        final JToolBar.Separator toolBar$Separator3 = new JToolBar.Separator();
        toolBar3.add(toolBar$Separator3);
        columnScan = new JButton();
        columnScan.setEnabled(false);
        columnScan.setIcon(new ImageIcon(getClass().getResource("/images/search.png")));
        columnScan.setMinimumSize(new Dimension(24, 24));
        columnScan.setPreferredSize(new Dimension(24, 24));
        columnScan.setText("");
        columnScan.setToolTipText("Perform an advanced scan...");
        toolBar3.add(columnScan);
        columnJump = new JButton();
        columnJump.setEnabled(false);
        columnJump.setIcon(new ImageIcon(getClass().getResource("/images/jump.png")));
        columnJump.setMinimumSize(new Dimension(24, 24));
        columnJump.setPreferredSize(new Dimension(24, 24));
        columnJump.setText("");
        columnJump.setToolTipText("Jump to a specific row number");
        toolBar3.add(columnJump);
        columnPopulate = new JButton();
        columnPopulate.setEnabled(false);
        columnPopulate.setIcon(new ImageIcon(getClass().getResource("/images/populate.png")));
        columnPopulate.setMinimumSize(new Dimension(24, 24));
        columnPopulate.setPreferredSize(new Dimension(24, 24));
        columnPopulate.setText("");
        columnPopulate.setToolTipText("Populate rows for the selected table");
        toolBar3.add(columnPopulate);
        final JScrollPane scrollPane2 = new JScrollPane();
        scrollPane2.setDoubleBuffered(true);
        panel5.add(scrollPane2, new GridConstraints(3, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        columnsTable = new JTable();
        columnsTable.setAutoCreateRowSorter(true);
        columnsTable.setAutoResizeMode(1);
        scrollPane2.setViewportView(columnsTable);
        final JPanel panel7 = new JPanel();
        panel7.setLayout(new GridLayoutManager(1, 1, new Insets(0, 0, 0, 0), -1, -1));
        panel5.add(panel7, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JToolBar toolBar4 = new JToolBar();
        toolBar4.setBorderPainted(false);
        toolBar4.setFloatable(false);
        panel7.add(toolBar4, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 20), null, 0, false));
        columnCheck = new JButton();
        columnCheck.setEnabled(false);
        columnCheck.setIcon(new ImageIcon(getClass().getResource("/images/select.png")));
        columnCheck.setMaximumSize(new Dimension(24, 24));
        columnCheck.setMinimumSize(new Dimension(24, 24));
        columnCheck.setPreferredSize(new Dimension(24, 24));
        columnCheck.setText("");
        columnCheck.setToolTipText("Select all columns");
        toolBar4.add(columnCheck);
        columnUncheck = new JButton();
        columnUncheck.setEnabled(false);
        columnUncheck.setIcon(new ImageIcon(getClass().getResource("/images/unselect.png")));
        columnUncheck.setMaximumSize(new Dimension(24, 24));
        columnUncheck.setMinimumSize(new Dimension(24, 24));
        columnUncheck.setPreferredSize(new Dimension(24, 24));
        columnUncheck.setText("");
        columnUncheck.setToolTipText("Unselect all columns");
        toolBar4.add(columnUncheck);
        final JToolBar.Separator toolBar$Separator4 = new JToolBar.Separator();
        toolBar4.add(toolBar$Separator4);
        columnNames = new WideComboBox();
        columnNames.setEnabled(true);
        columnNames.setMaximumSize(new Dimension(32767, 26));
        columnNames.setMinimumSize(new Dimension(50, 20));
        columnNames.setPreferredSize(new Dimension(-1, -1));
        columnNames.setToolTipText("Choose the column name (column family and qualifier)");
        toolBar4.add(columnNames);
        final JToolBar.Separator toolBar$Separator5 = new JToolBar.Separator();
        toolBar4.add(toolBar$Separator5);
        final JPanel panel8 = new JPanel();
        panel8.setLayout(new GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1));
        panel5.add(panel8, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JLabel label2 = new JLabel();
        label2.setText("Columns:");
        panel8.add(label2, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final Spacer spacer2 = new Spacer();
        panel8.add(spacer2, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_WANT_GROW, 1, null, null, null, 0, false));
        columnsNumber = new JLabel();
        columnsNumber.setText("0 of 0");
        panel8.add(columnsNumber, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JPanel panel9 = new JPanel();
        panel9.setLayout(new GridLayoutManager(2, 1, new Insets(0, 4, 0, 0), -1, -1));
        panel9.setAlignmentX(0.5f);
        panel9.setAlignmentY(0.5f);
        panel9.setMaximumSize(new Dimension(-1, -1));
        panel9.setMinimumSize(new Dimension(200, -1));
        panel9.setPreferredSize(new Dimension(-1, -1));
        innerSplitPane.setRightComponent(panel9);
        final JPanel panel10 = new JPanel();
        panel10.setLayout(new GridLayoutManager(1, 3, new Insets(0, 0, 0, 0), -1, -1));
        panel9.add(panel10, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, null, null, null, 0, false));
        final JLabel label3 = new JLabel();
        label3.setText("Rows:");
        panel10.add(label3, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
        final JToolBar toolBar5 = new JToolBar();
        toolBar5.setBorderPainted(false);
        toolBar5.setFloatable(false);
        panel10.add(toolBar5, new GridConstraints(0, 2, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 20), null, 0, false));
        rowOpen = new JButton();
        rowOpen.setEnabled(false);
        rowOpen.setIcon(new ImageIcon(getClass().getResource("/images/viewer.png")));
        rowOpen.setMinimumSize(new Dimension(24, 24));
        rowOpen.setPreferredSize(new Dimension(24, 24));
        rowOpen.setText("");
        rowOpen.setToolTipText("Open rows in external viewer");
        toolBar5.add(rowOpen);
        rowCopy = new JButton();
        rowCopy.setEnabled(false);
        rowCopy.setIcon(new ImageIcon(getClass().getResource("/images/copy.png")));
        rowCopy.setMaximumSize(new Dimension(49, 27));
        rowCopy.setMinimumSize(new Dimension(24, 24));
        rowCopy.setPreferredSize(new Dimension(24, 24));
        rowCopy.setText("");
        rowCopy.setToolTipText("Copy selected rows to the clipboard");
        toolBar5.add(rowCopy);
        final JToolBar toolBar6 = new JToolBar();
        toolBar6.setBorderPainted(false);
        toolBar6.setFloatable(false);
        panel10.add(toolBar6, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_FIXED, null, new Dimension(-1, 20), null, 0, false));
        rowsNumber = new JSpinner();
        rowsNumber.setDoubleBuffered(true);
        rowsNumber.setEnabled(true);
        rowsNumber.setMinimumSize(new Dimension(45, 24));
        rowsNumber.setPreferredSize(new Dimension(60, 24));
        toolBar6.add(rowsNumber);
        final JToolBar.Separator toolBar$Separator6 = new JToolBar.Separator();
        toolBar6.add(toolBar$Separator6);
        rowsVisible = new JLabel();
        rowsVisible.setText("?");
        toolBar6.add(rowsVisible);
        final JToolBar.Separator toolBar$Separator7 = new JToolBar.Separator();
        toolBar6.add(toolBar$Separator7);
        final JLabel label4 = new JLabel();
        label4.setText("of");
        toolBar6.add(label4);
        final JToolBar.Separator toolBar$Separator8 = new JToolBar.Separator();
        toolBar6.add(toolBar$Separator8);
        rowsTotal = new JLabel();
        rowsTotal.setText("?");
        toolBar6.add(rowsTotal);
        rowsNumberIcon = new JLabel();
        rowsNumberIcon.setIcon(new ImageIcon(getClass().getResource("/images/busy.gif")));
        rowsNumberIcon.setText("");
        rowsNumberIcon.setVisible(false);
        toolBar6.add(rowsNumberIcon);
        final JToolBar.Separator toolBar$Separator9 = new JToolBar.Separator();
        toolBar6.add(toolBar$Separator9);
        rowsPrev = new JButton();
        rowsPrev.setEnabled(false);
        rowsPrev.setIcon(new ImageIcon(getClass().getResource("/images/prev.png")));
        rowsPrev.setMaximumSize(new Dimension(49, 27));
        rowsPrev.setMinimumSize(new Dimension(24, 24));
        rowsPrev.setPreferredSize(new Dimension(24, 24));
        rowsPrev.setText("");
        rowsPrev.setToolTipText("Go to the prvious page");
        toolBar6.add(rowsPrev);
        rowsNext = new JButton();
        rowsNext.setEnabled(false);
        rowsNext.setIcon(new ImageIcon(getClass().getResource("/images/next.png")));
        rowsNext.setMaximumSize(new Dimension(49, 27));
        rowsNext.setMinimumSize(new Dimension(24, 24));
        rowsNext.setPreferredSize(new Dimension(24, 24));
        rowsNext.setText("");
        rowsNext.setToolTipText("Go to the next page");
        toolBar6.add(rowsNext);
        final JScrollPane scrollPane3 = new JScrollPane();
        panel9.add(scrollPane3, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
        rowsTable = new JTable();
        rowsTable.setAutoCreateRowSorter(true);
        rowsTable.setAutoResizeMode(0);
        rowsTable.setCellSelectionEnabled(true);
        rowsTable.setColumnSelectionAllowed(true);
        scrollPane3.setViewportView(rowsTable);
    }

    /**
     * @noinspection ALL
     */
    public JComponent $$$getRootComponent$$$() {
        return topPanel;
    }

    //endregion

    /**
     * Represents the direction of the loading operation.
     */
    private enum Direction {
        /**
         * Reload current data.
         */
        Current,
        /**
         * Load next data.
         */
        Forward,
        /**
         * Load previous data.
         */
        Backward
    }
}
